package com.colorfruit.framework.httpclient;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.*;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.*;


/**
 * Http连接池
 * @author  Odz
 *
 */
@Slf4j
public class HttpClientPool {

    public static void main(String[] args) {
        Map<String, String> paramsMap = new HashMap<>();
//        paramsMap.put("sid", "xxx");
//        paramsMap.put("content","yyy");
//        Map<String, String> header = new HashMap<>();
//        header.put("desKey", "dd");
//        header.put("sign", "kk");
//        header.put("randomId", UUID.randomUUID().toString());
//        header.put("organCode", "xiaocaimi");
//        LOGGER.info("请求参数：header:"+JSONObject.toJSONString(header)+",===body:"+JSONObject.toJSONString(paramsMap));
//        String encodeResult = HttpClientPool.getHttpClientPoolInstance().post("http://www.baidu.com", paramsMap, header);
//        LOGGER.info("响应结果：{}",encodeResult);
        HttpClientPool.getHttpClientPoolInstance().downloadImage("https://creditcloud.hxb.com.cn/hxypt/GenTokenImg.do?random=0.2654790838878578","E:\\temp\\ocr\\target\\hx\\temp.jpg");
    }

    /** 链接超时时间*/
    private static final int CONNECTION_TIME_OUT = 30000;

    /** 响应超时时间*/
    private static final int SOCKET_TIME_OUT = 300000;

    /** 设置连接超时时间 */
    public final static int DEFAULT_HTTP_CONNECTION_TIMEOUT = 30000;
    /** 设置获取连接的最大等待时间 */
    public final static int DEFAULT_HTTP_CONNECTION_REQUESTTIMEOUT = 30000;
    /**设置读取超时时间*/
    public final static int DEFAULT_HTTP_CONNECTION_SOCKETTIMEOUT = 300000;
    /** 最大连接数 */
    public final static int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
    /** 每个路由最大连接数  */
    public final static int DEFAULT_MAX_ROUTE_CONNECTIONS = 20;
    /** 重连次数*/
    public final static int RETRY_COUNT = 2;
    /** 字符编码*/
    private final static String CHAR_SET = "UTF-8";

    private PoolingHttpClientConnectionManager connectionPool = null;
    private static HttpPoolCleanThread scanThread = null;
    private CloseableHttpClient httpClient = null;
    private static volatile HttpClientPool httpClientPool = null;
    private   static final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SOCKET_TIME_OUT).setConnectTimeout(CONNECTION_TIME_OUT).build(); //设置请求和传输超时时间

    /**
     * 单例连接池
     * @return
     */
    public static HttpClientPool getHttpClientPoolInstance() {
        if(null == httpClientPool) {
            synchronized (HttpClientPool.class) {
                if(null == httpClientPool) {
                    httpClientPool = new HttpClientPool();
                    log.warn("{HttpClientPool} inited");
                }
            }
        }
        return httpClientPool;
    }

    private HttpClientPool()  {
        connectionPool = new PoolingHttpClientConnectionManager();
        connectionPool.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
        // 管理连接的基本单位是Route（路由），每个路由上都会维护一定数量的HTTP连接。
        // 这里的Route的概念可以理解为客户端机器到目标机器的一条线路，例如使用HttpClient的实现来分别请求 www.163.com 的资源和 www.sina.com 的资源就会产生两个route。
        // 缺省条件下对于每个Route，HttpClient仅维护2个连接，总数不超过20个连接，显然对于大多数应用来讲，都是不够用的，可以通过设置HTTP参数进行调整。
        connectionPool.setDefaultMaxPerRoute(DEFAULT_MAX_ROUTE_CONNECTIONS);
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(DEFAULT_HTTP_CONNECTION_REQUESTTIMEOUT)
                .setConnectTimeout(DEFAULT_HTTP_CONNECTION_TIMEOUT)
                .setSocketTimeout(DEFAULT_HTTP_CONNECTION_SOCKETTIMEOUT).build();
        httpClient = HttpClients.custom().
                setConnectionManager(connectionPool). //连接池
                setDefaultRequestConfig(requestConfig).  //超时等设置
                setRetryHandler(this.getRetryHandler()).  //重连设置
                build();
        this.startCleanThread();
    }

    /**
     * 重连设置
     * @return
     */
    private HttpRequestRetryHandler getRetryHandler() {
        //请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
            if (executionCount > RETRY_COUNT) {// 如果已经重试了RETRY_COUNT次，就放弃
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {// 连接超时
                log.warn("{HttpClientPool HttpRequestRetryHandler: ConnectTimeoutException, do retry}");
                return true;
            }

            if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                return true;
            }
            if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                return false;
            }
            if (exception instanceof InterruptedIOException) {// 超时
                return false;
            }
            if (exception instanceof UnknownHostException) {// 目标服务器不可达
                return false;
            }
            if (exception instanceof SSLException) {// ssl握手异常
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            // 如果请求是幂等的，就再次尝试
            return !(request instanceof HttpEntityEnclosingRequest);
        };
        return httpRequestRetryHandler;
    }

    /**
     * httpclient连接池定时扫描无效的连接， 系统启动时调用
     */
    private void startCleanThread() {
        //启动清理现场
        scanThread = new HttpPoolCleanThread(connectionPool);
        scanThread.start();
    }

    /**
     * 停掉连接池
     * @throws IOException
     */
    public void closePool()  {
        if(null != httpClient) {
            scanThread.shutdown();
            try {
                httpClient.close();
            } catch (IOException e) {
                log.error(" httpClientPool exception ", e);
            }
        }
    }

    /**
     * 释放http 资源
     * <p> CloseableHttpResponse.close()
     * <p> HttpPost.close()
     * @param response
     * @param httpPost
     */
    private void releaseResource(CloseableHttpResponse response, HttpPost httpPost) {
        try {
            if(null != response){
                response.close();
            }
            if(null != httpPost) {
                httpPost.releaseConnection();
            }
        } catch (IOException e) {
            log.error("Post close Exception");
        }
    }

    private void releaseResource(CloseableHttpResponse response, HttpGet httpGet) {
        try {
            if(null != response){
                response.close();
            }
            if(null != httpGet) {
                httpGet.releaseConnection();
            }
        } catch (IOException e) {
            log.error("Get close  Exception");
        }
    }

    /**
     * 打印htpp请求结果
     * @param url
     * @param result
     */
    private void getResponseResult(String url, String result) {
        log.info("Post request url:{}, result : {}  ",url,  (null!=result &&result.length()>1000) ? result.substring(0, 1000)+"..." : result );
    }

    private CloseableHttpClient getClient() {
        return this.httpClient;
    }

    /**
     * 发起Post请求
     * @param url POST请求的地址
     * @param content POST请求的内容
     * @return
     */
    public String post(String url, String content, Map<String, String> headerMap){
        HttpPost httpPost = null;
        CloseableHttpResponse response = null;
        String result = null;

        try {
            httpPost = new HttpPost(url);
            httpPost.setHeader(HTTP.CONTENT_TYPE, "application/json;charset=UTF-8");
            httpPost.setEntity(new StringEntity(content, CHAR_SET));
            if(null != headerMap && headerMap.size() > 0) {//头部header
                for(String key : headerMap.keySet()) {
                    httpPost.addHeader(key, headerMap.get(key));
                }
            }

            response = this.getClient().execute(httpPost, HttpClientContext.create());
            result = EntityUtils.toString(response.getEntity(), CHAR_SET);
            this.getResponseResult(url, result);
        } catch (Exception e) {
            log.error("Post [url ： {}，param:{}] 请求异常！ \n excepiton ： {} ",url,content, e);
        } finally {
            this.releaseResource(response, httpPost);
        }
        return result;
    }

    public String postJson(String url, Map<String, String> params, Map<String, String> headerMap){
        HttpPost httpPost = null;
        CloseableHttpResponse response = null;
        String result = null;

        String content = null;
        try {
            httpPost = new HttpPost(url);
            httpPost.setHeader(HTTP.CONTENT_TYPE, "application/json;charset=UTF-8");
            content = JSON.toJSONString(params);
            httpPost.setEntity(new StringEntity(content, CHAR_SET));
            if(null != headerMap && headerMap.size() > 0) {//头部header
                for(String key : headerMap.keySet()) {
                    httpPost.addHeader(key, headerMap.get(key));
                }
            }

            response = this.getClient().execute(httpPost, HttpClientContext.create());
            result = EntityUtils.toString(response.getEntity(), CHAR_SET);
            this.getResponseResult(url, result);
        } catch (Exception e) {
            log.error("Post [url ： {}，param:{}] 请求异常！ \n excepiton ： {} ",url, content, e);
        } finally {
            this.releaseResource(response, httpPost);
        }
        return result;
    }
    



    /**
     * 发起Post请求，带上而外头部参数
     * @param url POST请求的地址
     * @param params POST请求的参数
     * @param headerMap  header额外参数
     * @return
     */
    public String post(String url, Map<String, String> params, Map<String, String> headerMap){
        HttpPost httpPost = null;
        CloseableHttpResponse response = null;
        String result = null;

        List<NameValuePair> pair = new ArrayList<>();
        Set<String> keySet = params.keySet();
        for(String key : keySet) {
            pair.add(new BasicNameValuePair(key, params.get(key)));
        }

        try {
            httpPost = new HttpPost(url);
            httpPost.setEntity(new UrlEncodedFormEntity(pair, CHAR_SET));
            if(null != headerMap && headerMap.size() > 0) {//头部header
                for(String key : headerMap.keySet()) {
                    httpPost.addHeader(key, headerMap.get(key));
                }
            }

            response = this.getClient().execute(httpPost, HttpClientContext.create());
            result = EntityUtils.toString(response.getEntity(), CHAR_SET); //包含销毁操作
            this.getResponseResult(url, result);
        } catch (Exception e) {
            log.error("Post [url ： {}，param:{}] \n exception ： {} 请求异常！",url, JSONObject.toJSONString(params), e);
        } finally {
            this.releaseResource(response, httpPost);
        }
        return result;
    }
    /**
     * 发起Post请求，带上而外头部参数
     * @param url POST请求的地址
     * @param params POST请求的参数
     * @param headerMap  header额外参数
     * @return
     */
    public String post(String url, Object params, Map<String, String> headerMap){
    	HttpPost httpPost = null;
    	CloseableHttpResponse response = null;
    	String result = null;
    	
    	List<NameValuePair> pair = new ArrayList<>();
    	JSONObject jsonParam= (JSONObject) JSONObject.toJSON(params);
    	Set<String> keySet = jsonParam.keySet();
    	for(String key : keySet) {
    		pair.add(new BasicNameValuePair(key, jsonParam.get(key)==null?null:jsonParam.get(key).toString()));
    	}
    	
    	try {
    		httpPost = new HttpPost(url);
    		httpPost.setEntity(new UrlEncodedFormEntity(pair, CHAR_SET));
    		if(null != headerMap && headerMap.size() > 0) {//头部header
    			for(String key : headerMap.keySet()) {
    				httpPost.addHeader(key, headerMap.get(key));
    			}
    		}
    		
    		response = this.getClient().execute(httpPost, HttpClientContext.create());
    		result = EntityUtils.toString(response.getEntity(), CHAR_SET); //包含销毁操作
    		this.getResponseResult(url, result);
    	} catch (Exception e) {
    		log.error("Post [url ： {}，param:{}] \n exception ： {} 请求异常！",url, JSONObject.toJSONString(params), e);
    	} finally {
    		this.releaseResource(response, httpPost);
    	}
    	return result;
    }


    /**
     * post请求 传输带有文件的 http请求，在请求体中使用 multipart/form-data 格式来编码参数
     * @param url
     * @param params
     * @param headerMap
     * @return
     */
    public  String uploadPost(String url, Map<String, Object> params, Map<String, String> headerMap){
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpPost httpPost = null;
        String result = null;
        try {
            httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
            if(null != headerMap && headerMap.size() > 0) {//头部header
                for(String key : headerMap.keySet()) {
                    httpPost.addHeader(key, headerMap.get(key));
                }
            }
            httpClient = HttpClients.createDefault();
            MultipartEntityBuilder  builder = MultipartEntityBuilder.create();

            for(String key: params.keySet()) {
                if(null != params.get(key) &&  params.get(key).getClass().isAssignableFrom(File.class)) { //文件类型
                    // 把文件转换成流对象FileBody
                    builder.addPart(key, new FileBody((File) params.get(key)));  // 相当于<input type="file" name="file"/>
                } else {
                    // 相当于<input type="text" name="userName" value=userName>
                    builder.addPart(key, new StringBody(params.get(key).toString(), ContentType.create("text/plain", "UTF-8")));
                }
            }
            HttpEntity reqEntity = builder.build();
            httpPost.setEntity(reqEntity);

            // 发起请求 并返回请求的响应
            response = this.getClient().execute(httpPost, HttpClientContext.create());
            result = EntityUtils.toString(response.getEntity(), CHAR_SET); //包含销毁操作
            this.getResponseResult(url, result);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            this.releaseResource(response, httpPost);
        }
        return result;
    }

    public String get(String url, String param){
        HttpGet httpGet = null;
        CloseableHttpResponse response = null;
        String result = null;
        try {
            httpGet = new HttpGet(url + "?" + param);
            log.info("===========URL={}=======", httpGet.getURI().toString());
            response = httpClient.execute(httpGet);
            //得到响应体
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity);
        }catch ( Exception e){
            log.error("Get [url ： {}，param:{}] \n exception ： {} 请求异常！",url , JSONObject.toJSONString(param), e);
        }finally {
            this.releaseResource(response, httpGet);
        }
        return result;
    }

	public String get(String url, Map<String, String> params, Map<String, String> headerMap, String encoding){
		HttpGet httpGet = null;
		CloseableHttpResponse response = null;
		String result = null;

		List<NameValuePair> parameters = new ArrayList<>();
		if (params != null) {
			for (Map.Entry<String,String> entry: params.entrySet()){
				parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
		}
		String paramsStr;
		try {
			paramsStr = EntityUtils.toString(
					new UrlEncodedFormEntity(parameters, encoding == null ? Consts.UTF_8 : Charset.forName(encoding)));
			//创建Get请求
			httpGet = new HttpGet(url + "?" + paramsStr);
			if(null != headerMap && headerMap.size() > 0) {//头部header
				for(String key : headerMap.keySet()) {
					httpGet.addHeader(key, headerMap.get(key));
				}
			}
			//执行Get请求
            log.info("===========URL={}=======", httpGet.getURI().toString());
            Header[] allHeaders = httpGet.getAllHeaders();
            StringBuffer headString = new StringBuffer();
            for (Header header : allHeaders){
                headString.append(header.getName()).append(":").append(header.getValue());
            }
            log.info("==============headString={}==================", headString);
			response = httpClient.execute(httpGet);


			//得到响应体
			HttpEntity entity = response.getEntity();
			result = EntityUtils.toString(entity);
//			this.getResponseResult(url, result);
		} catch (IOException e) {
			log.error("Get [url ： {}，param:{}] \n exception ： {} 请求异常！",url , JSONObject.toJSONString(params), e);
		} finally {
			this.releaseResource(response, httpGet);
		}
		return result;
	}

    public String get(String url, Map<String, String> params, Map<String, String> headerMap){
		return get(url, params, headerMap, null);
    }

    /**
     * 下载图片
     * @param imageUrl
     * @return
     */
    public void downloadImage(String imageUrl,String outputFilePath){
        try{
            HttpGet get = new HttpGet(imageUrl);
            HttpResponse response = httpClient.execute(get);
            HttpEntity entity = response.getEntity();
            InputStream in = entity.getContent();

            final File outputFile = new File(outputFilePath);
            final FileOutputStream outputStream = new FileOutputStream(outputFile);
            int l = -1;
            byte[] tmp = new byte[1024];
            while ((l = in.read(tmp)) != -1) {
                outputStream.write(tmp, 0, l);
            }
            outputStream.flush();
            outputStream.close();

        }catch(Exception e1){
            log.error("",e1);
        }

    }


    /**
     * 以post 方式进行下载
     * @param url
     * @param params
     * @param headerMap
     * @return
     */
    public byte[] postDownStream(String url, Map<String, String> params, Map<String, String> headerMap){
        byte[] result = null;
        HttpPost httpPost = null;
        CloseableHttpResponse response = null;


        try {
            httpPost = new HttpPost(url);
            List<NameValuePair> pair = new ArrayList<>();
            Set<String> keySet = params.keySet();
            for(String key : keySet) {
                pair.add(new BasicNameValuePair(key, params.get(key)));
            }

            if(null != headerMap && headerMap.size() > 0) {//头部header
                for(String key : headerMap.keySet()) {
                    httpPost.addHeader(key, headerMap.get(key));
                }
            }
            httpPost.setEntity(new UrlEncodedFormEntity(pair, CHAR_SET));
            response = this.getClient().execute(httpPost, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toByteArray(entity);
        } catch (Exception e) {
            log.error("Post [url ： {}，param:{}] 请求异常！ \n excepiton ： {} ",url,params, e);
        } finally {
            this.releaseResource(response, httpPost);
        }
        return result;
    }




    private static void addHeaderParam(HttpPost httpPost, Map<String, String> headerMap) {
        for(String key : headerMap.keySet()) {//头部header
            httpPost.addHeader(key, headerMap.get(key));
        }
    }



    private static void savePicToDisk(InputStream in, String dirPath, String filePath) {

        try {
            File dir = new File(dirPath);
            if (dir == null || !dir.exists()) {
                dir.mkdirs();
            }

            //文件真实路径
            String realPath = dirPath.concat(filePath);
            File file = new File(realPath);
            if (file == null || !file.exists()) {
                file.createNewFile();
            }

            FileOutputStream fos = new FileOutputStream(file);
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = in.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            fos.flush();
            fos.close();

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


}
